/*
 * Copyright 2002-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose.web.impl.thread;

import javax.servlet.ServletRequest;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.util.WebUtils;

/**
 * Special {@link org.springframework.validation.DataBinder} to perform data
 * binding from servlet request parameters to JavaBeans, including support for
 * multipart files.
 * 
 * <p>
 * See the DataBinder/WebDataBinder superclasses for customization options,
 * which include specifying allowed/required fields, and registering custom
 * property editors.
 * 
 * <p>
 * Used by Spring Web MVC's BaseCommandController and MultiActionController.
 * Note that BaseCommandController and its subclasses allow for easy
 * customization of the binder instances that they use through overriding
 * <code>initBinder</code>.
 * 
 * <p>
 * Can also be used for manual data binding in custom web controllers: for
 * example, in a plain Controller implementation or in a MultiActionController
 * handler method. Simply instantiate a ServletRequestDataBinder for each
 * binding process, and invoke <code>bind</code> with the current ServletRequest
 * as argument:
 * 
 * <pre class="code"> MyBean myBean = new MyBean(); // apply binder to custom
 * target object ServletRequestDataBinder binder = new
 * ServletRequestDataBinder(myBean); // register custom editors, if desired
 * binder.registerCustomEditor(...); // trigger actual binding of request
 * parameters binder.bind(request); // optionally evaluate binding errors Errors
 * errors = binder.getErrors(); ...</pre>
 * 
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see #bind(javax.servlet.ServletRequest)
 * @see #registerCustomEditor
 * @see #setAllowedFields
 * @see #setRequiredFields
 * @see #setFieldMarkerPrefix
 * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder
 */
public class ServletRequestDataBinder extends WebDataBinder {

	private String prefix;

	/**
	 * Create a new ServletRequestDataBinder instance, with default object name.
	 * 
	 * @param target
	 *            the target object to bind onto (or <code>null</code> if the
	 *            binder is just used to convert a plain parameter value)
	 * @see #DEFAULT_OBJECT_NAME
	 */
	public ServletRequestDataBinder(Object target) {
		super(target);
	}

	/**
	 * Create a new ServletRequestDataBinder instance.
	 * 
	 * @param target
	 *            the target object to bind onto (or <code>null</code> if the
	 *            binder is just used to convert a plain parameter value)
	 * @param objectName
	 *            the name of the target object
	 */
	public ServletRequestDataBinder(Object target, String objectName) {
		super(target, objectName);
		prefix = objectName + '.';
	}

	/**
	 * Bind the parameters of the given request to this binder's target, also
	 * binding multipart files in case of a multipart request.
	 * <p>
	 * This call can create field errors, representing basic binding errors like
	 * a required field (code "required"), or type mismatch between value and
	 * bean property (code "typeMismatch").
	 * <p>
	 * Multipart files are bound via their parameter name, just like normal HTTP
	 * parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
	 * invoking a "setUploadedFile" setter method.
	 * <p>
	 * The type of the target property for a multipart file can be
	 * MultipartFile, byte[], or String. The latter two receive the contents of
	 * the uploaded file; all metadata like original file name, content type,
	 * etc are lost in those cases.
	 * 
	 * @param request
	 *            request with parameters to bind (can be multipart)
	 * @see org.springframework.web.multipart.MultipartHttpServletRequest
	 * @see org.springframework.web.multipart.MultipartFile
	 * @see #bindMultipartFiles
	 * @see #bind(org.springframework.beans.PropertyValues)
	 */
	public void bind(ServletRequest request) {
		MutablePropertyValues mpvs = new MutablePropertyValues(WebUtils
				.getParametersStartingWith(request, prefix));
		if (request instanceof MultipartHttpServletRequest) {
			MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
			bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
		}
		doBind(mpvs);
	}

	@Override
	protected void doBind(MutablePropertyValues mpvs) {
		// book.author.name的设置要自动使book.author设置一个author进去
		PropertyValue[] pvArray = mpvs.getPropertyValues();
		for (int i = 0; i < pvArray.length; i++) {
			PropertyValue pv = pvArray[i];
			String propertyName = pv.getName();
			int dot = propertyName.indexOf('.');
			while (dot != -1) {
				String field = propertyName.substring(0, dot);
				if (getPropertyAccessor().isWritableProperty(field)
						&& !mpvs.contains(field)) {
					Class<?> fieldType = getPropertyAccessor().getPropertyType(
							field);
					mpvs.addPropertyValue(field, BeanUtils
							.instantiateClass(fieldType));
				}
			}
		}
		//
		super.doBind(mpvs);
	}

	/**
	 * Treats errors as fatal.
	 * <p>
	 * Use this method only if it's an error if the input isn't valid. This
	 * might be appropriate if all input is from dropdowns, for example.
	 * 
	 * @throws ServletRequestBindingException
	 *             subclass of ServletException on any binding problem
	 */
	public void closeNoCatch() throws ServletRequestBindingException {
		if (getBindingResult().hasErrors()) {
			throw new ServletRequestBindingException(
					"Errors binding onto object '"
							+ getBindingResult().getObjectName() + "'",
					new BindException(getBindingResult()));
		}
	}

	// private AbstractPropertyBindingResult result;
	//
	// @Override
	// protected AbstractPropertyBindingResult getInternalBindingResult() {
	// if (result == null) {
	// result = new BeanPropertyBindingResult(this.getTarget(),
	// getObjectName());
	// }
	// return result;
	// }
}
